﻿using System;
using System.Text;

namespace Arex
{
    public static class Float2
    {
        /// <summary>
        /// Initialization of rectangular jagged array of float. All elements are zero by default.
        /// </summary>
        /// <param name="rows">Number of rows in result array.</param>
        /// <param name="cols">Number of columns in result array.</param>
        /// <returns>Rectangular jagged array of float.</returns>
        private static float[][] _init(int rows, int cols)
        {
            float[][] z = new float[rows][];
            for (int i = 0; i < rows; i++)
                z[i] = new float[cols];
            return z;
        }

        /// <summary>
        /// Checks if any array size equals to zero.
        /// </summary>
        /// <param name="a">Array of float.</param>
        /// <returns>True if array size equals zero, false otherwise.</returns>
        private static bool _has_zero_size(float[][] a)
        {
            return a.Length == 0 || a[0].Length == 0;
        }

        /// <summary>
        /// Check if two arrays have same size.
        /// </summary>
        /// <param name="a">First array of float.</param>
        /// <param name="b">Second array of float.</param>
        /// <returns>True if array sizes are different, false otherwise.</returns>
        private static bool _have_diff_size(float[][] a, float[][] b)
        {
            return a.Length != b.Length || a[0].Length != b[0].Length;
        }

        #region Queries

        /// <summary>
        /// Check if array is squared.
        /// This method checks only first row and first column assuming that array is regular.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>True if array is square, false otherwise.</returns>
        public static bool IsSquared(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                if (a.Length != a[0].Length) return false;
            return true;
        }

        /// <summary>
        /// Checks if all elements are equal to zero (i.e. when the array is initialized with zeros).
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>True if all elements are equal to zero.</returns>
        public static bool IsZero(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    if (a[i][j] != 0F) return false;
            return true;
        }

        #endregion

        #region Unary operations

        /// <summary>
        /// Negation of all elements in array.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] then b = a.Negate() returns [[-1, -2, -3], [-4, -5, -6]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Array of all elements with opposite sign.</returns>
        public static float[][] Negate(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = -a[i][j];
            return z;
        }

        /// <summary>
        /// Scalar inversion of all elements in array.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] then b = a.Invert() returns [[1, 0.5, 0.33333333], [0.25, 0.2, 0.16666667]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Array of all elements with value equals to fraction one over element.</returns>
        public static float[][] Invert(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = 1F / a[i][j];
            return z;
        }

        #endregion

        #region Element by element operations

        /// <summary>
        /// Scalar element-wise add operation.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] and b = 1 then c = a.Add(b) returns [[2, 3, 4], [5, 6, 7]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value to add.</param>
        /// <returns>Array of all elements with values equal to sum of element and float value.</returns>
        public static float[][] Add(this float[][] a, float b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] + b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise add operation.
        /// Example: if a = [[1, 2], [3, 4]] and b = [[3, 4], [5, 5]] then c = a.Add(b) returns [[4, 6], [8, 9]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value array to add.</param>
        /// <returns>Array of all elements with values equal to sum of correspondent elements.</returns>
        public static float[][] Add(this float[][] a, float[][] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] + b[i][j];
            return z;
        }

        /// <summary>
        /// Scalar element-wise subtract operation.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] and b = 1 then c = a.Sub(b) returns [[0, 1, 2], [3, 4, 5]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value to subtract.</param>
        /// <returns>Array of all elements with values equal to subtraction of element and float value.</returns>
        public static float[][] Sub(this float[][] a, float b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] - b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise subtract operation.
        /// Example: if a = [[1, 2], [3, 4]] and b = [[3, 4], [5, 5]] then c = a.Sub(b) returns [[-2, -2], [-2, -1]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value array to subtract.</param>
        /// <returns>Array of all elements with values equal to subtraction of correspondent elements.</returns>
        public static float[][] Sub(this float[][] a, float[][] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] - b[i][j];
            return z;
        }

        /// <summary>
        /// Scalar element-wise multiply operation.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] and b = 1 then c = a.Mul(b) returns [[1, 2, 3], [4, 5, 6]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value to multiply.</param>
        /// <returns>Array of all elements with values equal to multiplication of element and float value.</returns>
        public static float[][] Mul(this float[][] a, float b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] * b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise multiply operation.
        /// Example: if a = [[1, 2], [3, 4]] and b = [[1, 2], [1, 2]] then c = a.Mul(b) returns [[1, 4], [3, 8]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value array to multiply.</param>
        /// <returns>Array of all elements with values equal to multiplication of correspondent elements.</returns>
        public static float[][] Mul(this float[][] a, float[][] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] * b[i][j];
            return z;
        }

        /// <summary>
        /// Scalar element-wise divide operation.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] and b = 1 then c = a.Div(b) returns [[1, 2, 3], [4, 5, 6]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value to divide.</param>
        /// <returns>Array of all elements with values equal to division of element and float value.</returns>
        public static float[][] Div(this float[][] a, float b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();
            if (b == 0) throw new DivideByZeroException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] / b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise divide operation.
        /// Example: if a = [[1, 2], [3, 4]] and b = [[1, 2], [1, 2]] then c = a.Div(b) returns [[1, 1], [3, 2]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Float value array to divide.</param>
        /// <returns>Array of all elements with values equal to division of correspondent elements.</returns>
        public static float[][] Div(this float[][] a, float[][] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = a[i][j] / b[i][j];
            return z;
        }

        #endregion

        #region Conversion operations

        /// <summary>
        /// String representation of array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>String representation of array with default format.</returns> 
        public static string ToText(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            StringBuilder z = new StringBuilder();
            for (int i = 0; i < a.Length; i++)
            {
                for (int j = 0; j < a[0].Length; j++)
                {
                    z.Append(a[i][j]).Append(' ');
                }
                z.AppendLine();
            }
            return z.ToString();
        }

        /// <summary>
        /// Conversion of float type array into double type array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Double type array of elements equal to corresponding source array.</returns>
        public static double[][] ToDouble(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            double[][] z = new double[a.Length][];
            for (int i = 0; i < a.Length; i++)
                z[i] = new double[a[0].Length];
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[i][j] = Convert.ToDouble(a[i][j]);
            return z;
        }

        #endregion

        #region Copying

        /// <summary>
        /// Deep copy of array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Deep copy of source array.</returns>
        public static float[][] DeepCopy(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a.Length, a[0].Length);
            for (int i = 0; i < a.Length; i++)
                Buffer.BlockCopy(a[i], 0, z[i], 0, a[0].Length);

            return z;
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Initialize array with all elements equal to value.
        /// This method doesn't return value but performs inplace operation.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="val">Value to initialize array.</param>
        public static void ByValue(this float[][] a, float val)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    a[i][j] = val;
        }

        #endregion

        #region Math operations

        /// <summary>
        /// Sum of all elements in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Sum of all elements in array.</returns>
        public static float Sum(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float z = 0;
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z += a[i][j];
            return z;
        }

        /// <summary>
        /// Arithmetic mean of all elements in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Arithmetic mean of all elements in array.</returns>
        public static float Mean(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float z = 0;
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z += a[i][j];
            return z / (a.Length * a[0].Length);
        }

        /// <summary>
        /// Value of maximum element in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Value of maximum element in array.</returns>
        public static float Max(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float z = a[0][0];
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z = a[i][j] > z ? a[i][j] : z;
            return z;
        }

        /// <summary>
        /// Value of minimum element in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Value of minimum element in array.</returns>
        public static float Min(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float z = a[0][0];
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z = a[i][j] < z ? a[i][j] : z;
            return z;
        }

        /// <summary>
        /// Transpose array.
        /// Example: if a = [[1, 2, 3], [4, 5, 6]] then b = a.Trans() returns [[1, 4], [2, 5], [3, 6]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Transposition of array.</returns>
        public static float[][] Trans(this float[][] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            float[][] z = _init(a[0].Length, a.Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < a[0].Length; j++)
                    z[j][i] = a[i][j];
            return z;
        }

        /// <summary>
        /// Matrix multiplication of two arrays. This method requires proper arrays size.
        /// Example: if a = [[1, 2], [4, 5]] and b = [[2, 1], [0, 1]] then c = a.Matmul(b) returns [[2, 3], [8, 9]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Two dimensional array of float.</param>
        /// <returns>Matrix multiplication of two arrays.</returns>
        public static float[][] Matmul(this float[][] a, float[][] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (a[0].Length != b.Length) throw new ArgumentOutOfRangeException();

            float[][] z = _init(a.Length, b[0].Length);
            for (int i = 0; i < a.Length; i++)
                for (int j = 0; j < b[0].Length; j++)
                    for (int k = 0; k < a[0].Length; k++)
                        z[i][j] += a[i][k] * b[k][j];
            return z;
        }

        #endregion
    }
}
